package Traductor.Lexico;

import java.util.HashMap;
import java.util.Hashtable;

import Traductor.Lexico.Token.TipoToken;

public class LexicoEstados{
	
	//Atributos
	private String fuente;
	private Token token;
	private Estado estado;
	private Estado estadoInicial;
	private int pos;
	private int inicioLexema;
	private int linea;
	private Hashtable<String, Estado> estados;
	private HashMap<String, String[]> tabla; 
	
	//Constructora
	public LexicoEstados(){
		estados = new Hashtable<String, Estado>();
		tabla = new HashMap<String, String[]>();
		add("tipoVar", "natural");
		add("tipoVar", "entero");
		add("tipoVar", "real");
		add("tipoVar", "booleano");
		add("tipoVar", "caracter");
		add("tipoVar", "vector");
		add("tipoVar", "registro");
		add("tipoVar", "puntero");
		add("de", "de");
		add("null", "nulo");
		add("proc", "procedimiento");
		add("param", "param");
		add("paramVar", "paramvar");
		add("prog", "programa");
		add("dec", "declaraciones");
		add("var", "variable");
		add("tipo", "tipo");
		add("inst", "instrucciones");
		add("lee", "lee");
		add("escribe", "escribe");
		add("cast", "nat");
		add("cast", "ent");
		add("cast", "real");
		add("cast", "car");
		add("verdadero", "verdadero");
		add("falso", "falso");
		add("opO", "o");
		add("opY", "y");
		add("opNo", "no");
		add("opCondIf", "si");
		add("opCondElsif", "obien");
		add("opCondThen", "entonces");
		add("opCondElse", "sino");
		add("opCondEnd", "finsi");
		add("opBucleWhile", "mientras");
		add("opBucleFor", "desde");
		add("opBucleTo", "hasta");
		add("opBucleDo", "hacer");
		add("opReserve", "reserva");
		add("opFree", "libera");
	}
	
	// Metodos Privados
	private Token getNextToken() throws Exception{
		Token token = null;
		while(token == null){
			char actual = 0;
			if(pos == fuente.length()){
				if(estado instanceof EstadoAceptacion){
					String lexema = fuente.substring(inicioLexema, Math.min(pos, fuente.length()));
					token = estado.getToken(lexema);
					estado = estados.get(estado.getSiguienteEstado());
					return token;
				}
				return new Token(TipoToken.tk_EOF, null);
			}
			else if(pos < fuente.length())
				actual = fuente.charAt(pos);

			// Cuando se lee un '@' y estamos en el estado inicial seguimos leyendo caracteres hasta el final de linea.
			// No consideramos el comentario como un tipo de token.
			if(actual == '@' && estado.estadoInicial()){
				while((actual != '\n') && (pos < fuente.length()-1)){
					pos++;
					inicioLexema++;
					actual = fuente.charAt(pos);
				}
				if(pos == fuente.length()-1)
					return new Token(TipoToken.tk_EOF, null);
			}
			
			// Contador de lineas
			if(estado.estadoInicial() && (actual == '\n'))
				linea++;
			
			String lexema = fuente.substring(inicioLexema, Math.min(pos, fuente.length()));
			token = estado.getToken(lexema);
			int movimiento = estado.transita(actual);
			
			if((estado instanceof EstadoNoAceptacion) && (movimiento == 0))
				throw new Exception("LINE: " + linea + " - ERROR LEXICO: No existe ningun token del tipo <" + fuente.substring(inicioLexema, Math.min(pos+1, fuente.length())) + ">");

			pos += movimiento;
			estado = estados.get(estado.getSiguienteEstado());
			if(estado.estadoInicial())
				inicioLexema++;
		}
		inicioLexema = pos;
		return token;
	}
	
	private boolean esReservada(String tipo){
		if(!tabla.containsKey(tipo) || tipo == null) return false;
		else{
			int pos = 0;
			boolean esta = false;
			String[] st = tabla.get(tipo);
			while(pos<8 && !esta && st[pos] != null){
				esta = st[pos].equals(token.getLexema());
				pos++;
			}
			return esta;
		}
	}
	
	private void add(String key, String valor){
		String[] st;
		if(!tabla.containsKey(key)){
			st = new String[10];
			st[0] = valor;
			tabla.put(key, st);
		}
		else{
			int pos = 0;
			boolean rep = false;
			st = tabla.get(key);
			while(pos<10 && !rep && st[pos] != null){
				rep = st[pos].equals(valor);
				pos++;
			}
			if(!rep){
				st[pos] = valor;
				tabla.put(key, st);
			}
		}
	}
	
	//Metodos publicos
	public void inicializa(String fuente) throws Exception{
		//Inicializa el scanner con el codigo a analizar
		this.fuente = fuente;
		this.estado = this.estadoInicial;
		this.pos = 0;
		this.inicioLexema = 0;
		this.linea = 1;
		this.token = getNextToken();
	}

	public boolean consultaTipo(TipoToken tipoTK, String palRes){
		if(tipoTK.equals(TipoToken.tk_palabra) && palRes != null){
			if(esReservada(palRes)) return true; 
			else return false; 
		} 
		else return token.getTipo().equals(tipoTK); 
	}

	public Token reconoce(TipoToken tipo, String palRes) throws Exception{
		if(!consultaTipo(tipo, null))
			throw new Exception("LINE: " + linea + " - ERROR LEXICO: Token " + token.getLexema() + " no reconocido como tipo " + tipo);
 		if(tipo.compareTo(TipoToken.tk_palabra) == 0 && palRes != null)
			if(!esReservada(palRes))
				throw new Exception("LINE: " + linea + " - ERROR LEXICO: Token " + token.getLexema() + " ha de ser una palabra reservada de tipo: " + palRes);
		Token res = token;
		token = getNextToken();
		return res;
	}
	
	public void addEstado(Estado estado){
		this.estados.put(estado.getId(), estado);
		if(estado.estadoInicial())
			this.estadoInicial = estado;
	}
	
	public int getLinea(){
		return linea;
	}
	
}
